//------------------------------------------------------------------------------
// File: vcdplyer.cpp
//
// Desc: DirectShow sample code
//       - A VMR-enabled player application
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------

#include "Movie.h"

/******************************Public*Routine******************************\
* CMovie
*
* Constructors and destructors
*
\**************************************************************************/
CMovie::CMovie(HWND hwndApplication) :
m_hwndApp(hwndApplication),
m_MediaEvent(NULL),
m_Mode(MOVIE_NOTOPENED),
m_Fg(NULL),
m_Gb(NULL),
m_Mc(NULL),
m_Ms(NULL),
m_Me(NULL),
m_Wc(NULL),
m_TimeFormat(TIME_FORMAT_MEDIA_TIME)
{
}

CMovie::~CMovie()
{
}



/******************************Public*Routine******************************\
* AddVideoMixingRendererToFG
*
\**************************************************************************/
HRESULT
CMovie::AddVideoMixingRendererToFG()
{
	IBaseFilter* pBF = NULL;
	HRESULT hRes = CoCreateInstance(CLSID_VideoMixingRenderer9, NULL, CLSCTX_INPROC,
		IID_IBaseFilter, (LPVOID *)&pBF);

	if(SUCCEEDED(hRes))
	{
		hRes = m_Fg->AddFilter(pBF, L"Video Mixing Renderer 9");

		if(SUCCEEDED(hRes))
		{
			// Test VMRConfig, VMRMonitorConfig
			IVMRFilterConfig9* pConfig;
			HRESULT hRes2 = pBF->QueryInterface(IID_IVMRFilterConfig9, (LPVOID *)&pConfig);
			if(SUCCEEDED(hRes2))
			{
				hRes2 = pConfig->SetNumberOfStreams(2);
				hRes2 = pConfig->SetRenderingMode(VMR9Mode_Windowless);
				hRes2 = pConfig->SetRenderingPrefs(RenderPrefs_AllowOverlays);
				pConfig->Release();
			}

			IVMRMonitorConfig9* pMonitorConfig;
			HRESULT hRes3 = pBF->QueryInterface(IID_IVMRMonitorConfig9, (LPVOID *)&pMonitorConfig);
			if(SUCCEEDED(hRes3))
			{
				UINT iCurrentMonitor;
				HRESULT hr4 = pMonitorConfig->GetMonitor(&iCurrentMonitor);
				pMonitorConfig->Release();
			}

			hRes = pBF->QueryInterface(IID_IVMRWindowlessControl9, (LPVOID *)&m_Wc);
		}
	}

	if(pBF)
	{
		pBF->Release();
	}

	if(SUCCEEDED(hRes))
	{
		HRESULT hr = m_Wc->SetVideoClippingWindow(m_hwndApp);
		hr = m_Wc->SetAspectRatioMode(VMR_ARMODE_LETTER_BOX);
	}
	else
	{
		if(m_Wc)
		{
			m_Wc->Release();
			m_Wc = NULL;
		}
	}

	return hRes;
}


/******************************Public*Routine******************************\
* OpenMovie
*
\**************************************************************************/
HRESULT
CMovie::OpenMovie(
				  TCHAR *lpFileName
				  )
{
	USES_CONVERSION;
	IUnknown        *pUnk;
	HRESULT         hres;
	WCHAR           FileName[MAX_PATH];

	// Check input string
	if (lpFileName == NULL)
		return E_POINTER;

	hres = StringCchCopyW(FileName, NUMELMS(FileName), T2W(lpFileName));

	hres = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if(hres == S_FALSE)
		CoUninitialize();

	hres = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
		IID_IUnknown, (LPVOID *)&pUnk);

	if(SUCCEEDED(hres))
	{
		m_Mode = MOVIE_OPENED;
		hres = pUnk->QueryInterface(IID_IFilterGraph, (LPVOID *)&m_Fg);
		if(FAILED(hres))
		{
			pUnk->Release();
			return hres;
		}

		hres = AddVideoMixingRendererToFG();
		if(FAILED(hres))
		{
			m_Fg->Release(); m_Fg = NULL;
			return hres;
		}

		hres = pUnk->QueryInterface(IID_IGraphBuilder, (LPVOID *)&m_Gb);
		if(FAILED(hres))
		{
			pUnk->Release();
			m_Fg->Release(); m_Fg = NULL;
			m_Wc->Release(); m_Wc = NULL;
			return hres;
		}

		hres = m_Gb->RenderFile(FileName, NULL);
		if(FAILED(hres))
		{
			pUnk->Release();
			m_Fg->Release(); m_Fg = NULL;
			m_Wc->Release(); m_Wc = NULL;
			m_Gb->Release(); m_Gb = NULL;
			return hres;
		}

		hres = pUnk->QueryInterface(IID_IMediaControl, (LPVOID *)&m_Mc);
		if(FAILED(hres))
		{
			pUnk->Release();
			m_Fg->Release(); m_Fg = NULL;
			m_Wc->Release(); m_Wc = NULL;
			m_Gb->Release(); m_Gb = NULL;
			return hres;
		}

		//
		// Not being able to get the IMediaEvent interface doesn't
		// necessarly mean that we can't play the graph.
		//
		pUnk->QueryInterface(IID_IMediaEvent, (LPVOID *)&m_Me);
		GetMovieEventHandle();

		pUnk->QueryInterface(IID_IMediaSeeking, (LPVOID *)&m_Ms);
		pUnk->Release();
		return S_OK;
	}
	else
	{
		m_Fg = NULL;
	}

	return hres;
}


/******************************Public*Routine******************************\
* CloseMovie
*
\**************************************************************************/
DWORD
CMovie::CloseMovie(
				   )
{
	m_Mode = MOVIE_NOTOPENED;

	if(m_Mc)
	{
		if(m_Me)
		{
			m_MediaEvent = NULL;
			m_Me->Release();
			m_Me = NULL;
		}

		if(m_Ms)
		{
			m_Ms->Release();
			m_Ms = NULL;
		}

		if(m_Wc)
		{
			m_Wc->Release();
			m_Wc = NULL;
		}

		m_Mc->Release();
		m_Mc = NULL;

		if(m_Gb)
		{
			m_Gb->Release();
			m_Gb = NULL;
		}

		if(m_Fg)
		{
			m_Fg->Release();
			m_Fg = NULL;
		}
	}

	QzUninitialize();
	return 0L;
}


/******************************Public*Routine******************************\
* CMovie::GetNativeMovieSize
*
\**************************************************************************/
BOOL
CMovie::GetNativeMovieSize(
						   LONG *pcx,
						   LONG *pcy
						   )
{
	BOOL    bRet = FALSE;
	if(m_Wc)
	{
		bRet = (m_Wc->GetNativeVideoSize(pcx, pcy, NULL, NULL) == S_OK);
	}

	return bRet;
}


/******************************Public*Routine******************************\
* GetMoviePosition
*
\**************************************************************************/
BOOL
CMovie::GetMoviePosition(
						 LONG *px,
						 LONG *py,
						 LONG *pcx,
						 LONG *pcy
						 )
{
	BOOL    bRet = FALSE;

	if(m_Wc)
	{
		RECT src={0}, dest={0};
		HRESULT hr = m_Wc->GetVideoPosition(&src, &dest);
		*px = dest.left;
		*py = dest.right;
		*pcx = dest.right - dest.left;
		*pcy = dest.bottom - dest.top;
	}

	return bRet;
}

/******************************Public*Routine******************************\
* PutMoviePosition
*
\**************************************************************************/
BOOL
CMovie::PutMoviePosition(
						 LONG x,
						 LONG y,
						 LONG cx,
						 LONG cy
						 )
{
	BOOL    bRet = FALSE;

	RECT rc;
	SetRect(&rc, x, y, x + cx, y + cy);
	if(m_Wc)
	{
		bRet = (m_Wc->SetVideoPosition(NULL, &rc) == S_OK);
	}

	return bRet;
}


/******************************Public*Routine******************************\
* PlayMovie
*
\**************************************************************************/
BOOL
CMovie::PlayMovie(
				  )
{
	REFTIME rt, rtAbs, rtDur;
	HRESULT hr=S_OK;

	rt = GetCurrentPosition();
	rtDur = GetDuration();

	//
	// If we are near the end of the movie seek to the start, otherwise
	// stay where we are.
	//
	rtAbs = rt - rtDur;
	if(rtAbs < (REFTIME)0)
	{
		rtAbs = -rtAbs;
	}

	if(rtAbs < (REFTIME)1)
	{
		SeekToPosition((REFTIME)0,FALSE);
	}

	//
	// Change mode after setting m_Mode but before starting the graph
	//
	m_Mode = MOVIE_PLAYING;
	hr = m_Mc->Run();
	return TRUE;
}


/******************************Public*Routine******************************\
* PauseMovie
*
\**************************************************************************/
BOOL
CMovie::PauseMovie(
				   )
{
	m_Mode = MOVIE_PAUSED;

	HRESULT hr = m_Mc->Pause();
	return TRUE;
}


/******************************Public*Routine******************************\
* GetStateMovie
*
\**************************************************************************/

OAFilterState
CMovie::GetStateMovie(
					  )
{
	OAFilterState State;

	HRESULT hr = m_Mc->GetState(INFINITE,&State);
	return State;
}


/******************************Public*Routine******************************\
* StopMovie
*
\**************************************************************************/
BOOL
CMovie::StopMovie(
				  )
{
	m_Mode = MOVIE_STOPPED;
	HRESULT hr = m_Mc->Stop();
	return TRUE;
}


/******************************Public*Routine******************************\
* StatusMovie
*
\**************************************************************************/
EMovieMode
CMovie::StatusMovie(
					)
{
	if(m_Mc)
	{
		FILTER_STATE    fs;
		HRESULT         hr;

		hr = m_Mc->GetState(100, (OAFilterState *)&fs);

		// Don't know what the state is so just stay at old state.
		if(hr == VFW_S_STATE_INTERMEDIATE)
		{
			return m_Mode;
		}

		switch(fs)
		{
		case State_Stopped:
			m_Mode = MOVIE_STOPPED;
			break;

		case State_Paused:
			m_Mode = MOVIE_PAUSED;
			break;

		case State_Running:
			m_Mode = MOVIE_PLAYING;
			break;
		}
	}

	return m_Mode;
}


/******************************Public*Routine******************************\
* CanMovieFrameStep
*
\**************************************************************************/
BOOL
CMovie::CanMovieFrameStep()
{
	IVideoFrameStep* lpFS;
	HRESULT hr;

	hr = m_Fg->QueryInterface(__uuidof(IVideoFrameStep), (LPVOID *)&lpFS);
	if(SUCCEEDED(hr))
	{
		hr = lpFS->CanStep(0L, NULL);
		lpFS->Release();
	}

	return SUCCEEDED(hr);
}


/******************************Public*Routine******************************\
* GetMediaEventHandle
*
* Returns the IMediaEvent event hamdle for the filter graph iff the
* filter graph exists.
*
\**************************************************************************/
HANDLE
CMovie::GetMovieEventHandle(
							)
{
	HRESULT     hr;

	if(m_Me != NULL)
	{
		if(m_MediaEvent == NULL)
		{
			hr = m_Me->GetEventHandle((OAEVENT *)&m_MediaEvent);
		}
	}
	else
	{
		m_MediaEvent = NULL;
	}

	return m_MediaEvent;
}


/******************************Public*Routine******************************\
* GetMovieEventCode
*
\**************************************************************************/
long
CMovie::GetMovieEventCode()
{
	HRESULT hr;
	long    lEventCode;
	LONG_PTR    lParam1, lParam2;

	if(m_Me != NULL)
	{
		hr = m_Me->GetEvent(&lEventCode, &lParam1, &lParam2, 0);
		if(SUCCEEDED(hr))
		{
			hr = m_Me->FreeEventParams(lEventCode, lParam1, lParam2);
			return lEventCode;
		}
	}

	return 0L;
}


/******************************Public*Routine******************************\
* GetDuration
*
* Returns the duration of the current movie
*
\**************************************************************************/
REFTIME
CMovie::GetDuration()
{
	HRESULT hr;
	LONGLONG Duration;

	// Should we seek using IMediaSelection
	if(m_TimeFormat != TIME_FORMAT_MEDIA_TIME)
	{
		hr = m_Ms->GetDuration(&Duration);
		if(SUCCEEDED(hr))
		{
			return double(Duration);
		}
	}
	else if(m_Ms != NULL)
	{
		hr = m_Ms->GetDuration(&Duration);
		if(SUCCEEDED(hr))
		{
			return double(Duration) / UNITS;
		}
	}

	return 0;
}


/******************************Public*Routine******************************\
* GetCurrentPosition
*
* Returns the position of the current movie
*
\**************************************************************************/
REFTIME
CMovie::GetCurrentPosition()
{
	REFTIME rt = (REFTIME) 0;
	HRESULT hr;
	LONGLONG Position;

	// Should we return a media position
	if(m_TimeFormat != TIME_FORMAT_MEDIA_TIME)
	{
		hr = m_Ms->GetPositions(&Position, NULL);
		if(SUCCEEDED(hr))
		{
			return double(Position);
		}
	}
	else if(m_Ms != NULL)
	{
		hr = m_Ms->GetPositions(&Position, NULL);
		if(SUCCEEDED(hr))
		{
			return double(Position) / UNITS;
		}
	}

	return rt;
}


/*****************************Private*Routine******************************\
* SeekToPosition
*
\**************************************************************************/
BOOL
CMovie::SeekToPosition(
					   REFTIME rt,
					   BOOL bFlushData
					   )
{
	HRESULT hr;
	LONGLONG llTime = LONGLONG(m_TimeFormat == TIME_FORMAT_MEDIA_TIME ? rt * double(UNITS) : rt );

	if(m_Ms != NULL)
	{
		FILTER_STATE fs;
		hr = m_Mc->GetState(100, (OAFilterState *)&fs);

		hr = m_Ms->SetPositions(&llTime, AM_SEEKING_AbsolutePositioning, NULL, 0);

		// This gets new data through to the renderers
		if(fs == State_Stopped && bFlushData)
		{
			hr = m_Mc->Pause();
			hr = m_Mc->GetState(INFINITE, (OAFilterState *)&fs);
			hr = m_Mc->Stop();
		}

		if(SUCCEEDED(hr))
		{
			return TRUE;
		}
	}

	return FALSE;
}


/*****************************Public*Routine******************************\
* FindInterfaceFromFilterGraph
*
\**************************************************************************/
HRESULT
CMovie::FindInterfaceFromFilterGraph(
									 REFIID iid, // interface to look for
									 LPVOID *lp  // place to return interface pointer in
									 )
{
	IEnumFilters* pEF;
	IBaseFilter*  pFilter;

	// Grab an enumerator for the filter graph.
	HRESULT hr = m_Fg->EnumFilters(&pEF);

	if(FAILED(hr))
	{
		return hr;
	}

	// Check out each filter.
	while(pEF->Next(1, &pFilter, NULL) == S_OK)
	{
		hr = pFilter->QueryInterface(iid, lp);
		pFilter->Release();

		if(SUCCEEDED(hr))
		{
			break;
		}
	}

	pEF->Release();

	return hr;
}


/*****************************Public*Routine******************************\
* IsTimeFormatSupported
*
\**************************************************************************/
BOOL
CMovie::IsTimeFormatSupported(GUID Format)
{
	return m_Ms != NULL && m_Ms->IsFormatSupported(&Format) == S_OK;
}


/*****************************Public*Routine******************************\
* IsTimeSupported
*
\**************************************************************************/
BOOL
CMovie::IsTimeSupported()
{
	return m_Ms != NULL && m_Ms->IsFormatSupported(&TIME_FORMAT_MEDIA_TIME) == S_OK;
}


/*****************************Public*Routine******************************\
* GetTimeFormat
*
\**************************************************************************/
GUID
CMovie::GetTimeFormat()
{
	return m_TimeFormat;
}

/*****************************Public*Routine******************************\
* SetTimeFormat
*
\**************************************************************************/
BOOL
CMovie::SetTimeFormat(GUID Format)
{
	HRESULT hr = m_Ms->SetTimeFormat(&Format);
	if(SUCCEEDED(hr))
	{
		m_TimeFormat = Format;
	}
	return SUCCEEDED(hr);
}

/******************************Public*Routine******************************\
* SetFocus
*
\**************************************************************************/
void
CMovie::SetFocus()
{
	if(m_Fg)
	{
		// Tell the resource manager that we are being made active.  This
		// will then cause the sound to switch to us.  This is especially
		// important when playing audio only files as there is no other
		// playback window.
		IResourceManager* pResourceManager;

		HRESULT hr = m_Fg->QueryInterface(IID_IResourceManager, (void**)&pResourceManager);

		if(SUCCEEDED(hr))
		{
			IUnknown* pUnknown;

			hr = m_Fg->QueryInterface(IID_IUnknown, (void**)&pUnknown);

			if(SUCCEEDED(hr))
			{
				hr = pResourceManager->SetFocus(pUnknown);
				pUnknown->Release();
			}

			pResourceManager->Release();
		}
	}
}


/******************************Public*Routine******************************\
* RepaintVideo
*
\**************************************************************************/
BOOL
CMovie::RepaintVideo(
					 HWND hwnd,
					 HDC hdc
					 )
{
	BOOL bRet = FALSE;

	if (EC_COMPLETE == GetMovieEventCode())
	{
		m_Mode = MOVIE_COMPLETED;
		return TRUE;
	}

	if(m_Wc)
	{
		bRet = (m_Wc->RepaintVideo(hwnd, hdc) == S_OK);
	}

	return bRet;
}

/*****************************Private*Routine******************************\
* SetBorderClr
*
\**************************************************************************/
void
CMovie::SetBorderClr(COLORREF clr)
{
	m_Wc->SetBorderColor(clr);
}

bool CMovie::IsPlaying() const
{
	return MOVIE_PLAYING == m_Mode;
}

